package main

import (
	"encoding/csv"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/mail"
	"os"
	"strings"
)

var apiDomain string
var token string
var workDir string

func getEnv(key, dv string) string{
	v := os.Getenv(key)
	if v != "" {
		dv = v
	}
	return dv
}

func init() {
	apiDomain = getEnv("DOMAIN", "")
	token = getEnv("TOKEN", "")
	if apiDomain == "" || token == "" {
		log.Fatal("domain or token empty")
	}
	workDir = getEnv("WORK_DIR", "/workDir")
}


func main() {
	log.Println("begin reconcile admin")
	ag, err := reconcileAdmin()
	if err != nil {
		log.Fatal(err)
		return
	}
	log.Println("begin reconcile groups")
	err = reconcileGroups(ag)
	if err != nil {
		log.Fatal(err)
		return
	}
}

func reconcileGroups(ag *Group) (err error) {
	groupsLoc := fmt.Sprintf("%s/groups", workDir)
	_, err = os.Stat(groupsLoc)
	if err != nil {
		err = createError(groupsLoc, err)
		return
	}
	dir, err := ioutil.ReadDir(groupsLoc)
	if err != nil {
		return
	}
	PthSep := string(os.PathSeparator)
	for _, fi := range dir {
		if fi.IsDir() {
			continue
		}
		if !strings.HasSuffix(fi.Name(), ".csv") {
			continue
		}
		singleProjectLoc :=  groupsLoc+PthSep+fi.Name()
		singleProject := strings.Replace(fi.Name(), ".csv", "", -1)
		if singleProject == "" {
			err = createError(fi.Name(), fmt.Errorf("empty"))
		}
		log.Println("begin reconcile group", singleProject)
		err = reconcileGroup(ag, singleProject, singleProjectLoc)
		if err != nil {
		    return
		}

	}
	return
}
func reconcileGroup(ag *Group, name, loc string) (err error) {
	f, err := os.Open(loc)
	if err != nil {
		err = createError(loc, err)
		return
	}
	w := csv.NewReader(f)
	data, err := w.ReadAll()
	if err != nil {
		err = createError(loc, err)
		return
	}
	data = data[1:]
	// check group
	group, err := requestGroup(name, true)
	if err != nil {
		serr, ok := err.(*StatusCodeError)
		if !(ok && serr.StatusCode == http.StatusNotFound) {
			err = createError(fmt.Sprintf("get group %s failed", name), err)
			return
		}
		group = &Group{
			Name: name,
			Path: name,
			Visibility: "private", // key requirement
			ParentID: ag.ID,
		}
		err = createGroup(group)
		if err != nil {
			err = createError(fmt.Sprintf("create group %s failed", name), err)
			return
		}
		group, err = requestGroup(name, true) // re-qurey
		if err != nil {
			err = createError(fmt.Sprintf("get group %s failed", name), err)
			return
		}
	}
	// check users
	userEmailList := make([]string, 0)
	for _, s := range data {
		email := s[0]
		_, err = mail.ParseAddress(email)
		if err != nil {
			err = createError(loc, fmt.Errorf("%s is not a valid address", email))
			return
		}
		userEmailList = append(userEmailList, email)
	}
	repoUserEmail := fmt.Sprintf("%s@system.com", name)
	userEmailList = append(userEmailList, repoUserEmail)

	users, err := getGroupMembers(group)
	if err != nil {
		return
	}
	um := make(map[string]int) // user map
	for index, u := range users {
		um[u.Name] = index
	}
	cum := make(map[string]int) // user map from csv
	for index, email := range userEmailList {
		name := getNameFromEmail(email)
		_, ok := um[name]
		if ok {
			delete(um, name)
		}
		cum[name] = index
	}
	for _, u := range users {
		_, ok := cum[u.Name]
		if ok {
			delete(cum, u.Name)
		}
	}
	// add new user
	var repoUserId int
	for userName, index := range cum {
		userEmail := userEmailList[index]
		var user *User
		user, err = getUser(userName, userEmail)
		if err != nil {
			serr, ok := err.(*StatusCodeError)
			if !(ok && serr.StatusCode == http.StatusNotFound) {
				err = createError(fmt.Sprintf("get user %s failed", userEmail), err)
				return
			}
			user = &User{
				Email:    userEmail,
				Name:     userName,
				Username: userName,
				IsAdmin:  false,
			}
			log.Println("reconcile user[create]:", userEmail)
			if user.Name == name {
				rp := randChar(12)
				err = createUserWithPass(user, rp)
				if err == nil {
					log.Printf("repo-user %s' password is %s", user.Name, rp)
				}
			} else {
				err = createUser(user)
			}
			if err != nil {
				err = createError(fmt.Sprintf("create user %s failed", userEmail), err)
				return
			}
			user, err = getUser(userName, userEmail)
			if err != nil {
				err = createError(fmt.Sprintf("get user %s failed", userEmail), err)
				return
			}
		}
		log.Println("reconcile user[Add to user group]:", userEmail)
		err = addMember(group, user)
		if err != nil {
			err = createError(fmt.Sprintf("add user to user group %s failed", userEmail), err)
			return
		}
	}
	// remove user
	for _, index := range um {
		user := users[index]
		if user.Name == "Administrator" || user.ID == 1 {
			continue
		}
		log.Println("reconcile user[remove from user group]:", user.Name)
		err = removeMember(group, user)
		if err != nil {
			err = createError(fmt.Sprintf("remove user from iser group %s failed", user.Email), err)
			return
		}
	}

	// create repo
	log.Println("reconcile user[create repo]:", name)
	repoUser, err := getUser(name, repoUserEmail)
	if err != nil {
		err = createError(fmt.Sprintf("get repoUser for creating repo %s failed", name), err)
		return
	}
	repoUserId = repoUser.ID
	p, err := getRepo(name)
	if err != nil {
		serr, ok := err.(*StatusCodeError)
		if !(ok && serr.StatusCode == http.StatusNotFound) {
			err = createError(fmt.Sprintf("get repo %s failed", name), err)
			return
		}
		if repoUserId <= 0 {
			err = createError("creat repo", fmt.Errorf("create repo but user id not exists"))
			return
		}
		p, err = createRepo(name, repoUserId)
		if err != nil {
			err = createError(fmt.Sprintf("create repo %s failed", name), err)
			return
		}
	}
	err = deleteProtectedBranch(p, "master")
	if err != nil {
		serr, ok := err.(*StatusCodeError)
		if !(ok && serr.StatusCode == http.StatusNotFound) {
			err = createError(fmt.Sprintf("delete repo's protect branch master %s failed", name), err)
			return
		}
	}
	// add group
	log.Println("reconcile user[share repo to group]:", name)
	err = shareProject(p, ag, "40")
	if err != nil {
		err = createError(fmt.Sprintf("share repo to admin %s failed", name), err)
		return
	}

	err = shareProject(p, group, "40")
	if err != nil {
		err = createError(fmt.Sprintf("share repo to users %s failed", name), err)
		return
	}
	log.Printf("reconcile success for %s, link is %s/%s/%s\n", name, apiDomain, name, getRepoName(name))

	return
}

func createError(o string, err error) error {
	return fmt.Errorf("something wrong with %s:%s", o, err.Error())
}

func reconcileAdmin() (ag *Group, err error) {
	// check file
	adminCsvLoc := fmt.Sprintf("%s/admin.csv", workDir)
	_, err = os.Stat(adminCsvLoc)
	if err != nil {
		err = createError(adminCsvLoc, err)
		return
	}
	f, err := os.Open(adminCsvLoc)
	if err != nil {
		err = createError(adminCsvLoc, err)
		return
	}
	w := csv.NewReader(f)
	data, err := w.ReadAll()
	if err != nil {
		err = createError(adminCsvLoc, err)
		return
	}
	if len(data) <= 1 {
		err = createError(adminCsvLoc, fmt.Errorf("no data in admin.csv"))
		return
	}
	data = data[1:]
	adminEmailList := make([]string, 0)
	for _, s := range data {
		email := s[0]
		_, err = mail.ParseAddress(email)
		if err != nil {
			err = createError(adminCsvLoc, fmt.Errorf("%s is not a valid address", email))
			return
		}
		adminEmailList = append(adminEmailList, email)
	}
	// check admin group
	ag, err = requestAdminGroup()
	if err != nil {
		serr, ok := err.(*StatusCodeError)
		if ok && serr.StatusCode == http.StatusNotFound {
			_, err = createAdminGroup()
			if err != nil {
				err = createError("create admin group", err)
			}
			ag, err = requestAdminGroup() // re-fetch
			if err != nil {
				err = createError("fetch information of admin group", err)
				return
			}
		} else {
			err = createError("fetch information of admin group", err)
			return
		}
	}

	// check every user
	users, err := getGroupMembers(ag)
	if err != nil {
		return
	}
	um := make(map[string]int) // user map
	for index, u := range users {
		um[u.Name] = index
	}
	cum := make(map[string]int) // user map from csv
	for index, email := range adminEmailList {
		name := getNameFromEmail(email)
		_, ok := um[name]
		if ok {
			delete(um, name)
		}
		cum[name] = index
	}
	for _, u := range users {
		_, ok := cum[u.Name]
		if ok {
			delete(cum, u.Name)
		}
	}
	// add new admin
	for u, index := range cum {
		email := adminEmailList[index]
		var user *User
		log.Println("reconcile user:", email)
		user, err = getUser(u, email)
		if err != nil {
			serr, ok := err.(*StatusCodeError)
			if !(ok && serr.StatusCode == http.StatusNotFound) {
				err = createError(fmt.Sprintf("get user %s failed", user.Email), err)
				return
			}
			user = &User{
				Email:    email,
				Name:     u,
				Username: u,
				IsAdmin:  true,
			}
			log.Println("reconcile user[create]:", email)
			err = createUser(user)
			if err != nil {
				err = createError(fmt.Sprintf("create user %s failed", user.Email), err)
				return
			}
			user, err = getUser(u, email) // re query
			if err != nil {
				err = createError(fmt.Sprintf("get user %s failed", user.Email), err)
				return
			}
		}
		if !user.IsAdmin {
			user.IsAdmin = true
			log.Println("reconcile user[Assign admin role]:", email)
			err = modifyUserAdmin(user, true)
			if err != nil {
				err = createError(fmt.Sprintf("add user's admin permission %s failed", user.Email), err)
				return
			}
		}
		log.Println("reconcile user[Add to admin group]:", email)
		err = addMember(ag, user)
		if err != nil {
			err = createError(fmt.Sprintf("add user to admin group %s failed", user.Email), err)
			return
		}
	}
	// remove admin
	for _, index := range um {
		user := users[index]
		if user.Name == "Administrator" || user.ID == 1 {
			continue
		}
		log.Println("reconcile user[Remove admin role]:", user.Name)
		err = modifyUserAdmin(user, false)
		if err != nil {
			err = createError(fmt.Sprintf("remove user's admin permission %s failed", user.Email), err)
			return
		}
		log.Println("reconcile user[remove from admin group]:", user.Name)
		err = removeMember(ag, user)
		if err != nil {
			err = createError(fmt.Sprintf("remove user from admin group %s failed", user.Email), err)
			return
		}
	}
	return
}